package simpledb.execution;

import simpledb.algorithm.join.JoinStrategy;
import simpledb.algorithm.join.NestedLoopJoin;
import simpledb.common.DbException;
import simpledb.storage.Tuple;
import simpledb.storage.TupleDesc;
import simpledb.storage.TupleIterator;
import simpledb.transaction.TransactionAbortedException;

import java.util.ArrayList;
import java.util.NoSuchElementException;

/**
 * Join 算子实现了关系型数据库中的连接操作。
 * 它通过两个子算子读取元组，并根据给定的连接谓词（JoinPredicate）进行匹配，生成符合条件的连接结果。
 * 支持可插拔的连接策略（如嵌套循环、哈希连接等），默认使用嵌套循环连接。
 */
public class Join extends Operator {

    private static final long serialVersionUID = 1L;

    // 连接条件谓词（如 ON a.id = b.id），用于判断左右表元组是否匹配
    private JoinPredicate     joinPredicate;

    // 左侧输入流（外层表/驱动表），通常为嵌套循环中的外层循环表
    private OpIterator        child1;

    // 右侧输入流（内层表/被驱动表），通常为嵌套循环中的内层循环表
    private OpIterator        child2;

    // 输出元组的描述信息，由左右表的TupleDesc合并而成
    private TupleDesc         td;

    // 连接策略接口实现类，负责具体的连接逻辑（如嵌套循环、哈希连接等）
    private JoinStrategy      joinStrategy;

    // 连接结果的迭代器，用于按顺序返回连接后的元组
    private TupleIterator     iterator;

    /**
     * 构造函数：创建一个新的 Join 算子。
     *
     * @param p 连接谓词，包含连接条件的字段和比较操作（如EQUALS、LESS_THAN等）
     * @param child1 左侧输入算子（如SeqScan、Filter等），提供外层表数据
     * @param child2 右侧输入算子（如SeqScan、Filter等），提供内层表数据
     */
    public Join(JoinPredicate p, OpIterator child1, OpIterator child2) {
        this.joinPredicate = p;
        this.child1 = child1;
        this.child2 = child2;

        // 合并左右表的TupleDesc，生成输出元组的结构
        // 例如：左表字段A+B，右表字段C+D → 输出字段A+B+C+D
        final TupleDesc td1 = child1.getTupleDesc();
        final TupleDesc td2 = child2.getTupleDesc();
        final ArrayList<TupleDesc.TDItem> tdItems = new ArrayList<>();
        tdItems.addAll(td1.getDescList());
        tdItems.addAll(td2.getDescList());
        this.td = new TupleDesc(tdItems);
    }

    /**
     * 获取当前 Join 使用的连接谓词。
     * @return 连接谓词对象
     */
    public JoinPredicate getJoinPredicate() {
        return this.joinPredicate;
    }

    /**
     * 获取连接条件中左侧字段的名称（来自左表）。
     * 字段名通常包含别名或表名前缀（如"orders.id"），避免多表连接时的歧义。
     * @return 左表连接字段的名称
     */
    public String getJoinField1Name() {
        final int field1 = this.joinPredicate.getField1();
        return this.child1.getTupleDesc().getFieldName(field1);
    }

    /**
     * 获取连接条件中右侧字段的名称（来自右表）。
     * @return 右表连接字段的名称
     */
    public String getJoinField2Name() {
        final int field2 = this.joinPredicate.getField2();
        return this.child2.getTupleDesc().getFieldName(field2);
    }

    /**
     * 获取该算子输出元组的描述信息（字段类型和名称）。
     * 输出结构为左表字段 + 右表字段的组合。
     * @return 合并后的TupleDesc对象
     */
    public TupleDesc getTupleDesc() {
        return this.td;
    }

    /**
     * 打开 Join 算子及其所有子算子（左、右输入流）。
     * 初始化连接策略（默认使用NestedLoopJoin），并执行连接操作生成结果迭代器。
     * @throws DbException 数据库异常（如操作符已打开）
     * @throws TransactionAbortedException 事务被中止异常
     */
    public void open() throws DbException, NoSuchElementException, TransactionAbortedException {
        // 先打开左右子算子，确保数据可读取
        this.child1.open();
        this.child2.open();
        super.open();

        // 默认使用嵌套循环连接策略（可扩展为其他策略如HashJoin）
        this.joinStrategy = new NestedLoopJoin(child1, child2, this.td, this.joinPredicate);
        // 执行连接策略，生成结果迭代器
        this.iterator = this.joinStrategy.doJoin();
        // 打开结果迭代器，准备遍历连接结果
        this.iterator.open();
    }

    /**
     * 关闭 Join 算子及其关联的所有资源（子算子、连接策略、结果迭代器）。
     * 释放内存和文件句柄，防止资源泄漏。
     */
    public void close() {
        this.joinStrategy.close();
        this.iterator.close();
        this.child1.close();
        this.child2.close();
        super.close();
    }

    /**
     * 重置 Join 算子，重新开始遍历连接结果。
     * 通过关闭并重新打开算子实现重置，确保从头开始迭代。
     * @throws DbException 数据库异常
     * @throws TransactionAbortedException 事务被中止异常
     */
    public void rewind() throws DbException, TransactionAbortedException {
        close();
        open();
    }

    /**
     * 实现父类抽象方法，获取下一个符合条件的连接结果元组。
     * @return 下一个连接后的元组，若无则返回null
     */
    protected Tuple fetchNext() throws TransactionAbortedException, DbException {
        // 通过结果迭代器获取下一个元组（由连接策略生成）
        if (this.iterator.hasNext()) {
            return this.iterator.next();
        }
        return null;
    }

    /**
     * 获取该算子的所有子节点（左、右输入流）。
     * @return 子算子数组 [child1, child2]
     */
    @Override
    public OpIterator[] getChildren() {
        return new OpIterator[] { this.child1, this.child2 };
    }

    /**
     * 设置该算子的子节点（左、右输入流）。
     * 用于动态调整Join的输入源（如在查询优化中重新绑定表顺序）。
     * @param children 子算子数组（必须包含两个元素）
     */
    @Override
    public void setChildren(OpIterator[] children) {
        if (children.length == 2) {
            this.child1 = children[0];
            this.child2 = children[1];
        }
    }
}
